package com.texaco.chapasFutbol.entidades;

import static org.andengine.extension.physics.box2d.util.constants.PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT;

import java.util.ArrayList;
import java.util.List;

import org.andengine.engine.Engine.EngineLock;
import org.andengine.engine.handler.IUpdateHandler;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.primitive.Line;
import org.andengine.entity.primitive.Rectangle;
import org.andengine.entity.sprite.Sprite;
import org.andengine.extension.physics.box2d.PhysicsConnector;
import org.andengine.extension.physics.box2d.PhysicsFactory;
import org.andengine.extension.physics.box2d.PhysicsWorld;
import org.andengine.input.touch.TouchEvent;
import org.andengine.util.debug.Debug;

import android.graphics.Point;
import android.graphics.Rect;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.texaco.chapasFutbol.ChapasFutbolActivity;

/**
 * Se trata del objeto que especificará las reglas del juego, qué equipo tiene
 * el turno actual y todas aquelas vbles que requieran el correcto desarrollo
 * del juego.
 * 
 * @author texaco
 * 
 */
public class Game implements IUpdateHandler {

	/**
	 * TRUE si es el turno de las chapas locales.
	 */
	private boolean turnoLocal = true;

	/**
	 * Número de chapas de cada equipo.
	 */
	public static int NUM_CHAPAS_EQUIPO = 5;

	/*
	 * Posición de las chapas FIXME: HABRÍA QUE ESTABLECER UNAS POSICIONES
	 * RELATIVAS PARA NO VINCULARLO A UNA RESOLUCION DETERMINADA.
	 */
	public final static List<Point> POSICION_CHAPAS = new ArrayList<Point>() {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1137405217888490918L;

		{
			add(new Point(204, 162));
			add(new Point(490, 219));
			add(new Point(285, 400));
			add(new Point(490, 581));
			add(new Point(204, 638));
			/*
			 * El numero de puntos debe de equivaler al número de chapas.
			 */
		}
	};

	public final static Rect[] AREAS_GOL = {
			new Rect(1280 - 300, 0 + 200, 1280, 800 - 200),
			new Rect(0, 0 + 200, 0 + 300, 800 - 200) };

	/*
	 * createFixtureDef(final float pDensity, final float pElasticity, final
	 * float pFriction)
	 * 
	 * El primer parámetro define la densidad. Este parámetro sólo tiene sentido
	 * para los objetos dinámicos, porque los cuerpos estáticos y cinéticos
	 * actuan como si tuvieran infinita masa/densidad.
	 * 
	 * El segundo parámetro define la elasticidad (o rebote). En otras palabras,
	 * cuánto rebota cuando los cuerpos chocan. Normalmente se define entre el 0
	 * para que no rebote a 1f para la maxima velocidad de reflexión.
	 * 
	 * El tercer parámetro define la fricción. Normalmente se define a partir 0f
	 * (sin fricción) a 1f para la fricción fuerte..
	 */
	final static FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0,
			1f, 0.5f);

	private static final float VALOR_REBOTE_CHAPA = 8;
	private static final float VALOR_REBOTE_PELOTA = 3;
	private static final String CAMPO_SHAPE = "CAMPO_SHAPE";
	private static final String CHAPA_LOCAL_SHAPE = "CHAPA_LOCAL_SHAPE";
	private static final String CHAPA_VISITANTE_SHAPE = "CHAPA_VISITANTE_SHAPE";
	private static final String BALON_SHAPE = "BALON_SHAPE";
	private static final String MENSAJE_GOL = "GOL";

	/*
	 * Modos de juego
	 */
	private static final String MODO_GOLES = "MODO_GOLES"; // AQUÍ PUEDE HABER 2
															// VERTIENTES, AL
															// MEJOR DE X GOLES,
															// O AL PRIMERO QUE
															// LLEGUE A X GOLES
	private static final String MODO_TIEMPO = "MODO_TIEMPO";
	private static final String MODO_TURNOS = "MODO_TURNOS";

	// private String modoJuego = MODO_GOLES;
	private String modoJuego = MODO_TIEMPO;

	/*
	 * MODO GOLES
	 */
	private static final int GOLES_MAX = 3;

	/*
	 * MODO TIEMPO
	 */
	private static final int TIEMPO_MAX = 30; // Tiempo en SEGUNDOS(30)

	/*
	 * MODO TURNOS
	 */
	private static final int TURNOS_MAX = 2 * 2; // 10 turnos, hay que ponerlos
													// pares, para que ambos
													// equipos tengan las misma
													// oportunidades.

	/*
	 * Estados del juego.
	 */
	private static final String ESTADO_JUEGO_PARADO = "ESTADO_JUEGO_PARADO";
	private static final String ESTADO_JUEGO_ACTIVO = "ESTADO_JUEGO_ACTIVO";
	private static final String ESTADO_JUEGO_MOVIMIENTO = "ESTADO_JUEGO_MOVIMIENTO";
	private static final String ESTADO_JUEGO_FIN = "ESTADO_JUEGO_FIN";

	/*
	 * Equipo ganador.
	 */
	private static final String GANADOR_EMPATE = "GANADOR_EMPATE";
	private static final String GANADOR_VISITANTE = "GANADOR_VISITANTE";
	private static final String GANADOR_LOCAL = "GANADOR_LOCAL";

	/*
	 * Chapa fantasma
	 */
	private Sprite chapaFantasma;
	private boolean arrastrandoChapa = false;
	private Chapa chapaPulsada = null;
	private Line vectorFuerza = null;

	public int marcadorLocal = 0;
	public int marcadorVisitante = 0;
	public int tiempo = TIEMPO_MAX;

	private String estadoJuego = ESTADO_JUEGO_ACTIVO;

	private PhysicsWorld physicsWorld;

	private ChapasFutbolActivity activity;

	private Campo campo;

	private List<Chapa> chapas = new ArrayList<Chapa>();

	private Pelota pelota;

	/**
	 * Contador de turnos de la partida.
	 */
	private int turnos = 0;

	public Game(ChapasFutbolActivity activity) {
		this.activity = activity;
		this.physicsWorld = new PhysicsWorld(new Vector2(), true);

		/*
		 * Establecemos los margenes sobre los que van a rebotar las chapas y la
		 * pelota.
		 */
		iniciaBordes();
		iniciaSprites();
	}

	private void iniciaSprites() {
		/*
		 * Establecer todos los elementos de la escena.
		 */
		campo = new Campo(0, 0, activity.getShapesMaps().get(CAMPO_SHAPE),
				activity.getVertexBufferObjectManager());
		activity.getScene().attachChild(campo);

		for (Point punto : Game.POSICION_CHAPAS) {
			chapas.add(new ChapaLocal(punto.x - Chapa.WIDTH / 2, punto.y
					- Chapa.HEIGHT / 2, activity.getShapesMaps().get(
					CHAPA_LOCAL_SHAPE),
					activity.getVertexBufferObjectManager(), this,
					VALOR_REBOTE_CHAPA));
		}
		for (Point punto : Game.POSICION_CHAPAS) {
			chapas.add(new ChapaVisitante(Campo.WIDTH - punto.x - Chapa.WIDTH
					/ 2, Campo.HEIGHT - punto.y - Chapa.HEIGHT / 2, activity
					.getShapesMaps().get(CHAPA_VISITANTE_SHAPE), activity
					.getVertexBufferObjectManager(), this, VALOR_REBOTE_CHAPA));
		}
		for (Chapa chapa : chapas) {
			activity.getScene().attachChild(chapa);
			physicsWorld.registerPhysicsConnector(new PhysicsConnector(chapa,
					chapa.getBody(), true, true));
		}
		registrarAreaListeners();

		/*
		 * Ponemos la pelota en el campo.
		 */
		pelota = new Pelota(Campo.WIDTH / 2 - Pelota.WIDTH / 2, Campo.HEIGHT
				/ 2 - Pelota.HEIGHT / 2, activity.getShapesMaps().get(
				BALON_SHAPE), activity.getVertexBufferObjectManager(), this,
				VALOR_REBOTE_PELOTA);

		physicsWorld.registerPhysicsConnector(new PhysicsConnector(pelota,
				pelota.getBody(), true, true));

		activity.getScene().attachChild(pelota);

		activity.getScene().registerUpdateHandler(
				new TimerHandler(1, true, new ITimerCallback() {

					public void onTimePassed(TimerHandler pTimerHandler) {
						// Escribimos el código para iniciar el temporizador
						if (isJuegoActivo() && isModoTiempo()) {
							tiempo--;
							// FIXME: REQUIERE ACTUALIZAR EL HUD CON EL TIEMPO.
							Debug.d("Tiempo: " + tiempo);
							if (isFinJuego()) {
								estadoJuego = ESTADO_JUEGO_FIN;
								Debug.d("Fin del partido. Equipo ganador:"
										+ getEquipoGanador());
							}
						}

					}
				}));
	}

	private void iniciaBordes() {
		final Rectangle ground = new Rectangle(0, Campo.HEIGHT - 2,
				Campo.WIDTH, 2, activity.getVertexBufferObjectManager());
		final Rectangle roof = new Rectangle(0, 0, Campo.WIDTH, 2,
				activity.getVertexBufferObjectManager());
		final Rectangle left = new Rectangle(0, 0, 2, Campo.HEIGHT,
				activity.getVertexBufferObjectManager());
		final Rectangle right = new Rectangle(Campo.WIDTH - 2, 0, 2,
				Campo.HEIGHT, activity.getVertexBufferObjectManager());

		PhysicsFactory.createBoxBody(this.physicsWorld, ground,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.physicsWorld, roof,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.physicsWorld, left,
				BodyType.StaticBody, wallFixtureDef);
		PhysicsFactory.createBoxBody(this.physicsWorld, right,
				BodyType.StaticBody, wallFixtureDef);
	}

	/**
	 * Indica si es el turno de los locales.
	 * 
	 * @return
	 */
	public boolean isTurnoLocal() {
		return turnoLocal;
	}

	/**
	 * Indica si es turno de los visitantes.
	 * 
	 * @return
	 */
	public boolean isTurnoVisitante() {
		return !turnoLocal;
	}

	/**
	 * Cambia de turno de juego.
	 */
	public void cambiaTurno() {
		turnos++;
		turnoLocal = !turnoLocal;
	}

	public int getMarcadorLocal() {
		return this.marcadorLocal;
	}

	public int getMarcadorVisitante() {
		return this.marcadorVisitante;
	}

	public PhysicsWorld getPhysicsWorld() {
		return physicsWorld;
	}

	/**
	 * Método que establece la posicón inicial de los recursos.
	 */
	private void inicializaRecursos() {
		/*
		 * Chapas y Pelota.
		 */
		pelota.getBody().setTransform(
				(Campo.WIDTH / 2) / PIXEL_TO_METER_RATIO_DEFAULT,
				(Campo.HEIGHT / 2) / PIXEL_TO_METER_RATIO_DEFAULT, 0);
		pelota.getBody().setLinearVelocity(new Vector2());
		pelota.getBody().setAngularVelocity(0);

		int indLocal = 0;
		int indVisitante = 0;
		int pX;
		int pY;
		for (Chapa chapa : chapas) {
			Point punto = null;
			if (chapa instanceof ChapaLocal) {
				punto = Game.POSICION_CHAPAS.get(indLocal++);
				pX = punto.x;
				pY = punto.y;
			} else {
				punto = Game.POSICION_CHAPAS.get(indVisitante++);
				pX = Campo.WIDTH - punto.x;
				pY = Campo.HEIGHT - punto.y;
			}
			chapa.getBody().setTransform(pX / PIXEL_TO_METER_RATIO_DEFAULT,
					pY / PIXEL_TO_METER_RATIO_DEFAULT, 0);
			chapa.getBody().setLinearVelocity(new Vector2());
			chapa.getBody().setAngularVelocity(0);
		}
	}

	/**
	 * Método que implementa el comportamiento de gol.
	 */
	public void golLocal() {
		if (isJuegoActivo()) {
			marcadorLocal++;
		}
		turnoLocal = false;
		mostrarMensaje(MENSAJE_GOL);
		pararJuego();

		if (isModoGoles() && isFinJuego()) {
			estadoJuego = ESTADO_JUEGO_FIN;
			Debug.d("Fin del partido. Equipo ganador:" + getEquipoGanador());
		}
	}

	/**
	 * Método que implementa el comportamiento de gol.
	 */
	public void golVisitante() {
		if (isJuegoActivo()) {
			marcadorVisitante++;
		}
		turnoLocal = true;
		mostrarMensaje(MENSAJE_GOL);
		pararJuego();
		if (isModoGoles() && isFinJuego()) {
			estadoJuego = ESTADO_JUEGO_FIN;
			Debug.d("Fin del partido. Equipo ganador:" + getEquipoGanador());
		}
	}

	public String getEquipoGanador() {
		if (marcadorLocal == marcadorVisitante) {
			return GANADOR_EMPATE;
		} else if (marcadorLocal < marcadorVisitante) {
			return GANADOR_VISITANTE;
		}
		return GANADOR_LOCAL;
	}

	private boolean isFinJuego() {
		if (isModoGoles()) {
			return (marcadorLocal + marcadorVisitante) >= GOLES_MAX;
		} else if (isModoTurnos()) {
			return turnos >= TURNOS_MAX;
		} else if (isModoTiempo()) {
			return tiempo <= 0;
		}
		return false;
	}

	/*
	 * Método que para el juego, generalmente tras un gol(se podría poner como
	 * pausa). Requiere que se reanude posteriormente.
	 */
	private void pararJuego() {
		Debug.d("Se para el juego.");
		estadoJuego = ESTADO_JUEGO_PARADO;
	}

	/**
	 * Método que permite mostrar un mensaje por pantalla. Generalmente para
	 * informar de que se ha marcado un gol. TODO:
	 * 
	 * @param mensaje
	 */
	private void mostrarMensaje(String mensaje) {
		if (mensaje.equals(MENSAJE_GOL)) {
			activity.actualizaMarcador();
			activity.showMensajeGol(); // FIXME: Esto se crea así por que si
										// intentas crear el mensaje durante el
										// juego, casca.
		} else {
			Debug.d("Mensaje no definido!!");
		}
	}

	/**
	 * Método que permite reanudar el juego. Generalmente despues de parar el
	 * juego por un gol u otro evento.
	 */
	public void reanudarJuego() {
		inicializaRecursos();
		estadoJuego = ESTADO_JUEGO_ACTIVO;
		registrarAreaListeners();
	}

	public ChapasFutbolActivity getActivity() {
		return activity;
	}

	public boolean isJuegoParado() {
		return estadoJuego.equals(ESTADO_JUEGO_PARADO);
	}

	public boolean isJuegoActivo() {
		return estadoJuego.equals(ESTADO_JUEGO_ACTIVO)
				|| estadoJuego.equals(ESTADO_JUEGO_MOVIMIENTO);
	}

	private void eliminarAreaListeners() {
		for (Chapa chapa : chapas) {
			activity.getScene().unregisterTouchArea(chapa);
		}
	}

	private void registrarAreaListeners() {
		for (Chapa chapa : chapas) {
			activity.getScene().registerTouchArea(chapa);
		}
	}

	/**
	 * Método que nueve la chapa fantasma creada cuando se pulsa y arrastra una
	 * chapa.
	 * 
	 * @param pSceneTouchEvent
	 */
	public void mueveChapaFantasma(TouchEvent pSceneTouchEvent) {
		if (chapaFantasma != null) {
			chapaFantasma.setPosition(
					pSceneTouchEvent.getX() - Chapa.WIDTH / 2,
					pSceneTouchEvent.getY() - Chapa.HEIGHT / 2);

		}
	}

	/**
	 * Método que muestra por pantalla un vector indicando la direcicón y la
	 * fuerza con la que se lanzara la chapa al soltar.
	 * 
	 * @param pSceneTouchEvent
	 */
	public void actualizaVectorResultante(TouchEvent pSceneTouchEvent) {
		/*
		 * Calculamos la diferencia
		 */
		float cenX = chapaPulsada.getX() + Chapa.WIDTH / 2;
		float cenY = chapaPulsada.getY() + Chapa.HEIGHT / 2;
		float diffX = cenX - pSceneTouchEvent.getX();
		float diffY = cenY - pSceneTouchEvent.getY();
		if (vectorFuerza == null) {
			vectorFuerza = new Line(cenX, cenY, cenX + diffX, cenY + diffY,
					activity.getVertexBufferObjectManager());
			activity.getScene().attachChild(vectorFuerza);
		} else {
			vectorFuerza.setPosition(cenX, cenY, cenX + diffX, cenY + diffY);
		}
	}

	/**
	 * Crea una chapa fantasma que será la que se mueva segun se va arrastrando.
	 * 
	 * @param pSceneTouchEvent
	 */
	public void chapaPulsada(Chapa chapa, TouchEvent pSceneTouchEvent) {
		chapaPulsada = chapa;
		chapaFantasma = new Sprite(pSceneTouchEvent.getX(),
				pSceneTouchEvent.getX(), activity.getShapesMaps().get(
						BALON_SHAPE), activity.getVertexBufferObjectManager());
		activity.getScene().attachChild(chapaFantasma);
		this.arrastrandoChapa = true;
		eliminarAreaListeners();
	}

	/**
	 * Borra la chapa fantasma
	 */
	public void borraChapaFantasma() {
		if (chapaFantasma != null) {
			final EngineLock engineLock = activity.getEngine().getEngineLock();
			engineLock.lock();

			/* Now it is save to remove the entity! */
			activity.getScene().detachChild(chapaFantasma);
			activity.getScene().detachChild(vectorFuerza);
			chapaFantasma.dispose();
			chapaFantasma = null;
			vectorFuerza.dispose();
			vectorFuerza = null;

			engineLock.unlock();
		}
	}

	/**
	 * Método que informa de que la chapa ha sido soltada.
	 */
	public void chapaSoltada() {
		cambiaTurno();
		borraChapaFantasma();
		arrastrandoChapa = false;
		// registrarAreaListeners();
	}

	public void moverChapa(TouchEvent pSceneTouchEvent) {
		chapaPulsada.mover(pSceneTouchEvent);
		estadoJuego = ESTADO_JUEGO_MOVIMIENTO;
	}

	public boolean isArrastrandoChapa() {
		return arrastrandoChapa;
	}

	public void onUpdate(float pSecondsElapsed) {
		if (estadoJuego.equals(ESTADO_JUEGO_MOVIMIENTO)) {
			boolean moviendose = false;
			for (Chapa chapa : chapas) {
				if (chapa.getBody().isAwake()) {
					moviendose = true;
					break;
				}
			}
			if (!moviendose) {
				registrarAreaListeners();
				estadoJuego = ESTADO_JUEGO_ACTIVO;
			}
		} else if (isJuegoActivo() && isModoTurnos()) {
			if (isFinJuego()) {
				estadoJuego = ESTADO_JUEGO_FIN;
				Debug.d("Fin del partido. Equipo ganador:" + getEquipoGanador());
			}
		}
	}

	public void reset() {

	}

	public boolean isModoGoles() {
		return modoJuego.equals(MODO_GOLES);
	}

	public boolean isModoTiempo() {
		return modoJuego.equals(MODO_TIEMPO);
	}

	public boolean isModoTurnos() {
		return modoJuego.equals(MODO_TURNOS);
	}

	public boolean isJuegoFinalizado() {
		return estadoJuego.equals(ESTADO_JUEGO_FIN);
	}

	/**
	 * Metodo que reinicia los marcadores y contadores del juego y vuelve a
	 * colocar las piezas en su sitio para comenzar otro partido.
	 */
	public void reiniciarJuego() {
		iniciarMarcadores();
		activity.actualizaMarcador();
		reanudarJuego();
	}

	/**
	 * Método que pone a 0 todos los marcadores de juego.
	 */
	private void iniciarMarcadores() {
		marcadorLocal = 0;
		marcadorVisitante = 0;
		turnoLocal = true;
		turnos = 0;
		tiempo = TIEMPO_MAX;
	}
}
